package Project3;

import Project3.analysis.*;
import Project3.node.*;
import java.util.*;


class SymbolTableVariableSweep extends DepthFirstAdapter
{
    private String tempName;
    private String tempType;
    private SymbolTable symbolTable;
    private Method tempMethod;

 	public SymbolTableVariableSweep(SymbolTable table) {
        this.symbolTable = table;
        tempMethod = null;
	}
	
	/*
	* stmt stmtseq
	* stuff; things; ideas;
	*/
	public void caseAAStmtseq(AAStmtseq node){
		node.getStmt().apply(this);
		node.getStmtseq().apply(this);
	}

	/*
	* id idopt
	* id <stuff and things>
	*/
	public void caseACStmt(ACStmt node){
		this.tempName = node.getId().getText();
		node.getIdopt().apply(this);
	}

	/*
	* if lparen boolean rparen lcurly stmtseq rcurly optionalelse
	* if(4>5){doSomething(); thisThing();}else{stuff;}
	*/
	public void caseADStmt(ADStmt node){
		node.getBoolean().apply(this); //ensure it is able to be compared and such

        this.symbolTable.AddBlockScope();
		node.getStmtseq().apply(this);
        this.symbolTable.RemoveBlockScope();

        this.symbolTable.AddBlockScope();
		node.getOptionalelse().apply(this);
        this.symbolTable.RemoveBlockScope();
	}

    /*
    * while lparen boolean rparen then lcurly stmtseq rcurly optionalelse
    * WHILE( 1 < 4 ) THEN { stuff() };
    */
    public void caseAEStmt(AEStmt node){
        node.getBoolean().apply(this);

        this.symbolTable.AddBlockScope();
        node.getStmtseq().apply(this);
        this.symbolTable.RemoveBlockScope();
    }

    public void caseAFStmt(AFStmt node){
        //for loop, find a way to declare this variable (only temporary like)
        String temp = node.getId().getText();
        this.tempType = "";
        node.getExpr().apply(this);
        this.symbolTable.CheckDeclaredAndTypeForVar(temp, this.tempType, "for(" + temp + ":=" + this.tempType + "; <condition>; <statement>){", this.tempMethod);

        node.getBoolean().apply(this);
        node.getForexpr().apply(this);

        this.symbolTable.AddBlockScope();
        node.getStmtseq().apply(this);
        this.symbolTable.RemoveBlockScope();
    }

    //PUT(VAR);
    public void caseAHStmt(AHStmt node){
        this.symbolTable.CheckDeclaredVar(node.getId().getText(), this.tempMethod);
    }

    //RETURN STATEMENT
    public void caseAIStmt(AIStmt node){
        this.tempType = "";
        node.getExpr().apply(this);

		this.symbolTable.CheckReturnType(tempType, this.tempMethod);
    }

    public void caseAJStmt(AJStmt node){
        this.symbolTable.CheckDeclaredFunction(node.getId().getText());
        //enter method
        Method temp = this.tempMethod;
        this.tempMethod = this.symbolTable.getMethod(node.getId().getText());

        //"execute method"
        node.getCurlystmtseq().apply(this);

        //exit method
        this.tempMethod = temp;
    }

    public void caseALStmt(ALStmt node){
		node.getExpr().apply(this);
		node.getLf().apply(this);
		node.getCases().apply(this);
		node.getLh().apply(this);
    }

    /*
    * assign anychars semicolon
    * someId:= "STUFF_LOL";
    */
    public void caseAAIdopt(AAIdopt node){
        this.symbolTable.CheckDeclaredAndTypeForVar(this.tempName, "STRING", this.tempName + " := " + node.getAnychars().getText() + ";", this.tempMethod);
    }

    /*
    * assign assignrule
    */
    public void caseABIdopt(ABIdopt node){
        node.getAssignrule().apply(this);
        //Existance of variable is checked further down
    }

    /*
    * idlist colon type semicolon
    * this, that, theOther: STRING
    */
    public void caseACIdopt(ACIdopt node){
        this.tempType = "";
        node.getType().apply(this); //determine type
        node.getIdlist().apply(this);

        Variable v = new Variable(tempName, tempType);

		if(!this.symbolTable.CheckDuplicateVarDeclaration(this.tempMethod, v)){
        	this.symbolTable.addVar(v, this.tempMethod);
		}

    }

    /*
    * postinc semicolon
    * durr++;
    */
    public void caseADIdopt(ADIdopt node){
        //ENSURE THAT THY VARIABLE IS EXISTING AND A INT
        this.symbolTable.CheckDeclaredAndTypeForVar(this.tempName, "INT", this.tempName+"++;", this.tempMethod);
    }

    /*
    * postdec semicolon
    * durr--;
    */
    public void caseAEIdopt(AEIdopt node){
        this.symbolTable.CheckDeclaredAndTypeForVar(this.tempName, "INT", this.tempName+"--;", this.tempMethod);
    }

	private ArrayList<String> varTypeList = new ArrayList<String>();
	private ArrayList<String> varNameList = new ArrayList<String>();
    public void caseAFIdopt(AFIdopt node){
        //entering method.
        //Check that method is declared
        //compare parameters types to methods;
        if(this.symbolTable.CheckDeclaredFunction(this.tempName))
		{
			this.varTypeList.clear();
            this.varNameList.clear();
        	node.getVars().apply(this);

			if(this.symbolTable.compareParams(this.tempName, this.varTypeList)){
				//Check that all variables passed into the method actually exist
				this.symbolTable.CheckIfAllDeclaredAndType(this.varNameList, this.varTypeList, this.tempMethod);
			}
		}
    }

	/*
	* expr semicolon
	* <expr>;
	*/
	public void caseAAAssignrule(AAAssignrule node){
		//expression and semicolon
        tempType = "";
        node.getExpr().apply(this);

        this.symbolTable.CheckDeclaredAndTypeForVar(tempName, tempType, tempName + ":= <expression>", this.tempMethod);
	}

	/*
	* af:quote id as:quote semicolon
	* "stuff";
	*/
	public void caseABAssignrule(ABAssignrule node){
		this.symbolTable.CheckDeclaredAndTypeForVar(tempName, "STRING", tempName + " := " + node.getId().getText(), this.tempMethod);
	}

    /*
    * get semicolon
    * GET();
    */
    public void caseACAssignrule(ACAssignrule node){
        this.symbolTable.CheckDeclaredAndTypeForVar(tempName, "INT", tempName + " := GET();", this.tempMethod);
    }

    /*
    * comma id idlist
    */
    public void caseAAIdlist(AAIdlist node){
        Variable v = new Variable(node.getId().getText(), this.tempType);

		if(!this.symbolTable.CheckDuplicateVarDeclaration(this.tempMethod, v)){
			this.symbolTable.addVar(v, this.tempMethod);
		}

		node.getIdlist().apply(this);
    }

    /*
    * else lcurly stmtseq rcurly
    * else{ stuff(); }
    * NOTE: Block scope is handled above with the IF
    */
    public void caseAAOptionalelse(AAOptionalelse node){
        node.getStmtseq().apply(this);
    }

    /*
    * id postinc
    * id++
    */
    public void caseAAForexpr(AAForexpr node){
        this.symbolTable.CheckDeclaredAndTypeForVar(
            node.getId().getText(), 
            "INT", 
            "for(<variable>; <condition>; " + node.getId().getText() + "++)", 
            this.tempMethod);
    }

    /*
    * id postdec
    * id--
    */
    public void caseABForexpr(ABForexpr node){
        this.symbolTable.CheckDeclaredAndTypeForVar(
            node.getId().getText(), 
            "INT", 
            "for(<variable>; <condition>; " + node.getId().getText() + "--)",
            this.tempMethod);
    }

    /*
    * id assign expr
    * id := id + 1;
    */
    public void caseACForexpr(ACForexpr node){
        this.tempType = "";
        node.getExpr().apply(this);
        this.symbolTable.CheckDeclaredAndTypeForVar(
            node.getId().getText(), 
            tempType, 
            "for(<variable>; <condition>; " + node.getId().getText() + " := <expression>) ",
            this.tempMethod);
    }

    /*
    * lcurly stmtseq rcurly
    * { stmt(); stmt(); } OR ;
    * NOTE: I believe blocking is handled above
    */
    public void caseAACurlystmtseq(AACurlystmtseq node){
        node.getStmtseq().apply(this);
    }

    /*
    * semicolon
    *
    */
    public void caseABCurlystmtseq(ABCurlystmtseq node){
        //do nothing
    }

    /*
    * break semicolon
    * BREAK;
    */
    public void caseAAOptionalbreak(AAOptionalbreak node){
        //do nothing, because break statement
    }

	public void caseAACases(AACases node){
		node.getStmtseq().apply(this);
		node.getCases().apply(this);
	}

	//varTypeList
    public void caseAAVars(AAVars node){
        node.getVarlist().apply(this);
    }

    public void caseAAVarlist(AAVarlist node){
		node.getVarlist().apply(this);
		tempType = "";
		node.getType().apply(this);
		varTypeList.add(tempType);
		varNameList.add(node.getId().getText());
    }

    public void caseABVarlist(ABVarlist node){
		tempType = "";
		node.getType().apply(this);
		varTypeList.add(tempType);
		varNameList.add(node.getId().getText());
    }

	/*
	* expr addop term
	* 2 + 2;  || 2 - 2;  || stuff+things;
	*/
	public void caseAAExpr(AAExpr node){
		node.getExpr().apply(this);
		node.getTerm().apply(this);
	}

	/*
	* term
	* 4, id, whatever
	*/
	public void caseABExpr(ABExpr node){
		node.getTerm().apply(this);
	}

	public void caseAATerm(AATerm node){
		node.getTerm().apply(this);
		node.getFactor().apply(this);
	}

	public void caseABTerm(ABTerm node){
		node.getFactor().apply(this);
	}

	public void caseAAFactor(AAFactor node){
		node.getExpr().apply(this);
	}

	public void caseABFactor(ABFactor node){
		node.getFactor().apply(this);
	}

	public void caseACFactor(ACFactor node){
		//ensure type is int
		if(tempType.equals("")){
			tempType = "INT"; //we are not considering a variable today
		}
		else{
			this.symbolTable.typeCheck("INT", tempType);
		}
	}

	public void caseADFactor(ADFactor node){
		if(tempType.equals("")){
			tempType = "REAL"; //we are not considering a variable, this can happen in IF statements
		}
		else{
			this.symbolTable.typeCheck("REAL", tempType);
		}
	}

	public void caseAEFactor(AEFactor node){
        if(tempType == ""){
			if(this.symbolTable.CheckDeclaredVar(node.getId().getText(), this.tempMethod))
			{
				tempType = this.symbolTable.getVarFromAllScopes(node.getId().getText(), this.tempMethod).getType();
			}
        }
		else
		{
			this.symbolTable.CheckDeclaredAndTypeForVar(
            	node.getId().getText(), 
            	tempType, tempName + ":= <expression containing " + node.getId().getText() + ">",
            	this.tempMethod);
		}
	}

	public void caseAFFactor(AFFactor node){
		//function call, check return type and params and so on.
        //Check that method is declared
        //compare parameters types to methods;
		String functionName = node.getId().getText();
        if(this.symbolTable.CheckDeclaredFunction(functionName))
		{
			this.varTypeList.clear();
        	node.getVars().apply(this);

			if(this.symbolTable.compareParams(functionName, this.varTypeList)){

				this.symbolTable.CheckIfAllDeclaredAndType(this.varNameList, this.varTypeList, this.tempMethod);
			}
			tempType = this.symbolTable.getMethod(functionName).getType();
		}
	}

	public void caseAABoolean(AABoolean node){
		//GOOD TO GO
		tempType = "BOOLEAN";
	}

	public void caseABBoolean(ABBoolean node){
		//GOOD TO GO
		tempType = "BOOLEAN";
	}

	public void caseACBoolean(ACBoolean node){
		//evaluate expressions
		tempType = "";
		tempName = "";
		node.getCa().apply(this);
		node.getCb().apply(this);
	}

    public void caseAAType(AAType node){
        tempType = "INT";
    }

    public void caseABType(ABType node){
        tempType = "REAL";
    }

    public void caseACType(ACType node){
        tempType = "STRING";
    }

    public void caseADType(ADType node){
        tempType = "BOOLEAN";
    }

    public void caseAEType(AEType node){
        tempType = "VOID";
    }

}
